#include "dod.h"

apr_shm_t * dod_global_info_shm_ptr; //shared mem ptr.

apr_proc_mutex_t * dod_global_info_mutex_ptr; //mutex ptr.

apr_file_t * dod_forbidden_log_ptr; //forbidden log

extern apr_pool_t * dod_pchild;
extern apr_pool_t * dod_preq;
extern apr_uint16_t dod_cfg_rfl_range_time;
extern apr_uint16_t dod_cfg_rfl_count; 

/*assgin the stat ip info*/
#define DOD_STAT_IP_INFO_INIT(stat_rec_ptr, idx, in_addr, rcount, cur_time) \
    stat_rec_ptr->ips[idx].ip_addr = in_addr;         \
    stat_rec_ptr->ips[idx].request_count = rcount; \
    stat_rec_ptr->ips[idx].first_request_time = cur_time;


/*assign the forbidden ip info*/
#define DOD_FORBIDDEN_IP_INFO_INIT(forbidden_rec_ptr, idx, in_addr, rcount, cur_time) \
    forbidden_rec_ptr->ips[idx].ip_addr = in_addr;         \
    forbidden_rec_ptr->ips[idx].forbidden_count = rcount; \
    forbidden_rec_ptr->ips[idx].last_forbidden_time = cur_time;


//acquire mutex    
#define LOCK() \
    rv = apr_proc_mutex_lock(dod_global_info_mutex_ptr); \
    if(rv != APR_SUCCESS){\
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, "DOD: acquire mutex lock error! mutex ptr val: %p", dod_global_info_mutex_ptr);\
        return DOD_CHECKED_ALLOWED;\
    }

//release mutex
#define UNLOCK()  \
    rv = apr_proc_mutex_unlock(dod_global_info_mutex_ptr); \
if(rv != APR_SUCCESS){ \
    ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, "DOD: release mutex lock error!"); \
}

/*execute in parent process*/
APR_DECLARE(apr_status_t) dod_shm_create(apr_pool_t * pool)
{
    apr_status_t rv;
    char * shared_mem; 
    if(DOD_GLOBAL_INFO_MUTEX_FILE){
        apr_shm_remove(DOD_GLOBAL_INFO_SHM_FILE, pool); //remove the exists shm.
    }

    rv = apr_shm_create(&dod_global_info_shm_ptr, DOD_GLOBAL_INFO_SIZE, DOD_GLOBAL_INFO_SHM_FILE, pool/*pool ptr*/);
    if(rv != APR_SUCCESS){
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, "DOD: create shared memory error!");
        return rv;
    }
    if(!(shared_mem = apr_shm_baseaddr_get(dod_global_info_shm_ptr))){
        ap_log_error(APLOG_MARK, APLOG_ERR, APR_INCHILD, NULL, "DOD: get shared memory baseaddr error! shm_ptr_val: %p", shared_mem);
    }else{
    memset(shared_mem, 0, DOD_GLOBAL_INFO_SIZE);
    return APR_SUCCESS;
    }
}

/*execute in parent process*/
APR_DECLARE(apr_status_t) dod_mutex_create(apr_pool_t * pool)
{
    apr_status_t rv;
    rv = apr_proc_mutex_create(&dod_global_info_mutex_ptr, DOD_GLOBAL_INFO_MUTEX_FILE, DOD_MUTEX_LOCK_TYPE, pool);
    if(rv != APR_SUCCESS){
        ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, "DOD: create mutex lock error!");
        return rv;
    }else{ 
        //set permissions
#if APR_USE_SYSVSEM_SERIALIZE
        if (DOD_MUTEX_LOCK_TYPE == APR_LOCK_DEFAULT ||
                DOD_MUTEX_LOCK_TYPE == APR_LOCK_SYSVSEM) {
#else
        if (DOD_MUTEX_LOCK_TYPE == APR_LOCK_SYSVSEM) {
#endif
            rv = unixd_set_proc_mutex_perms(dod_global_info_mutex_ptr);
            if (rv != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_EMERG, rv, NULL,
                           "Couldn't set permissions on cross-process lock; ",
                           "check User and Group directives");
                return rv;
            }
        }
        return APR_SUCCESS;
    }
}

APR_DECLARE(dod_global_info_t *) dod_global_info_open()
{
    apr_status_t rv;
    void * shared_mem;
    if(!(shared_mem = apr_shm_baseaddr_get(dod_global_info_shm_ptr))){
        ap_log_error(APLOG_MARK, APLOG_ERR, APR_INCHILD, NULL, "DOD: get shared memory baseaddr error! %p", shared_mem);
        return NULL;
    }else{
        if(rv == APR_SUCCESS){ /*Create the shared memory first time!*/
        }
        return (dod_global_info_t *)shared_mem;
    }
}

APR_DECLARE(apr_uint16_t) dod_check(char * ip)
{
    dod_global_info_t * dod_global_info_ptr;
    dod_global_stat_info_t * stat_rec_ptr;
    dod_global_forbidden_info_t * forbidden_rec_ptr;
    apr_uintip_t current_ip_addr;
    apr_uint16_t i, min_frequency_idx = 0;
    float min_frequency_val = 0, current_frequency_val, current_range_time/*minutes*/;
    apr_time_t current_time, one_minute_usecs;
    apr_uint16_t stat_range_time = dod_cfg_rfl_range_time > 0 ? dod_cfg_rfl_range_time : DOD_DEFAULT_STAT_RANGE_TIME/*Unit: minute*/, 
                 max_request_count_limit = dod_cfg_rfl_count > 0 ? dod_cfg_rfl_count : DOD_DEFAULT_REQUEST_COUNT_LIMIT; 
    apr_status_t rv; 
    //current time
    current_time = apr_time_now();

    one_minute_usecs = APR_USEC_PER_SEC * 60; //usecs of one minute.

    current_ip_addr = inet_addr(ip); // client ip from char to int

    dod_global_info_ptr = dod_global_info_open();

    if(dod_global_info_ptr == NULL){
        return DOD_CHECKED_ALLOWED;
    }

    stat_rec_ptr = &dod_global_info_ptr->stat_rec;
    forbidden_rec_ptr = &dod_global_info_ptr->forbidden_rec;

    LOCK();//lock the shared memmory
    
    DOD_DEBUG("check if in forbidden list!");
    if(dod_check_in_forbidden_list(forbidden_rec_ptr, current_ip_addr) 
            == DOD_CHECKED_FORBIDDEN){
        UNLOCK();
        return DOD_CHECKED_FORBIDDEN;
    }

    //to record requesting client ip info
    for(i=0; i<DOD_STAT_HOST_MAX_NUM; i++){
        
        /*check if attack*/
        DOD_DEBUG("check if attack!");
        if(stat_rec_ptr->ips[i].request_count 
                >= max_request_count_limit){
            dod_global_forbidden_info_update(forbidden_rec_ptr, stat_rec_ptr->ips[i].ip_addr, current_time);
            dod_forbidden_log(stat_rec_ptr->ips[i].ip_addr, current_time); //log forbidden ip info
            DOD_STAT_IP_INFO_INIT(stat_rec_ptr, i, 0, 0, 0);
            if(current_ip_addr == stat_rec_ptr->ips[i].ip_addr){
                UNLOCK();
                return DOD_CHECKED_FORBIDDEN;
            }
        }

        /*initial the stat ip info*/
        DOD_DEBUG("init stat ip \n");
        if(current_time - stat_rec_ptr->ips[i].first_request_time 
                >= one_minute_usecs * stat_range_time/*minutes*/){
            DOD_STAT_IP_INFO_INIT(stat_rec_ptr, i, 0, 0, 0);
        }

        //add the request_count
        DOD_DEBUG("add request count");
        if(current_ip_addr == stat_rec_ptr->ips[i].ip_addr){
            stat_rec_ptr->ips[i].request_count++;
            UNLOCK();
            return DOD_CHECKED_ALLOWED;
        }

        //find the replace ip idx. 
        DOD_DEBUG("find replace ip \n");
        current_range_time = (float)(current_time - stat_rec_ptr->ips[i].first_request_time)                                           / one_minute_usecs;

        current_frequency_val = (float)stat_rec_ptr->ips[i].request_count / current_range_time;
        if(min_frequency_val < current_frequency_val){
            min_frequency_idx = i;
            min_frequency_val = current_frequency_val;
        }
    }
    //initial or replace the stat ip info.
    DOD_STAT_IP_INFO_INIT(stat_rec_ptr, min_frequency_idx, current_ip_addr, 1, current_time);
    
    UNLOCK();
    
    return DOD_CHECKED_ALLOWED;
}

APR_DECLARE(void) dod_global_forbidden_info_update(dod_global_forbidden_info_t * forbidden_rec_ptr, apr_uintip_t ip_addr, apr_time_t current_time)
{
    apr_uint16_t i, oldest_forbidden_idx = 0;
    apr_time_t oldest_forbidden_time = current_time;
    for(i=0; i<DOD_FORBIDDEN_HOST_MAX_NUM; i++){
        if(forbidden_rec_ptr->ips[i].ip_addr == ip_addr){
            forbidden_rec_ptr->ips[i].last_forbidden_time = current_time;
            forbidden_rec_ptr->ips[i].forbidden_count++;
            return;
        }
        if(oldest_forbidden_time > forbidden_rec_ptr->ips[i].last_forbidden_time){
            oldest_forbidden_time = forbidden_rec_ptr->ips[i].last_forbidden_time;
            oldest_forbidden_idx = i;
        }
    }
    DOD_FORBIDDEN_IP_INFO_INIT(forbidden_rec_ptr, oldest_forbidden_idx, ip_addr, 1, current_time); 
}

APR_DECLARE(apr_uint16_t) dod_check_in_forbidden_list(dod_global_forbidden_info_t * forbidden_rec_ptr, apr_uintip_t ip_addr)
{
    apr_uint16_t i;
    for(i=0; i<DOD_FORBIDDEN_HOST_MAX_NUM; i++){
        if(ip_addr == forbidden_rec_ptr->ips[i].ip_addr){
            return DOD_CHECKED_FORBIDDEN;
        }
    }
    return DOD_CHECKED_ALLOWED;
}

APR_DECLARE(apr_status_t) dod_forbidden_log_open(apr_pool_t * pool)
{
    apr_status_t rv;
    char * dod_forbidden_log_file = (char *)ap_server_root_relative(pool, DOD_FORBIDDEN_LOG_FILE); 
    rv = apr_file_open(&dod_forbidden_log_ptr, dod_forbidden_log_file, APR_CREATE|APR_WRITE|APR_APPEND, APR_OS_DEFAULT, pool);
    return rv;
}

APR_DECLARE(static apr_status_t) dod_forbidden_log(apr_uintip_t ip_addr, apr_time_t forbidden_time)
{
    char * dod_forbidden_log_fmt = "%s - - [%u-%u-%u %u:%u:%u]\n"; //"ip str - - [time str] - - [request count in current dod circle]"
    char * ip_str;
    char * log_str;
    apr_status_t rv;
    apr_uint16_t max_len = 100;
    apr_size_t * slen_ptr, slen;
    apr_time_exp_t lt_time;
   
    //ip_addr to str
    ip_str = (char *)apr_pstrdup(dod_preq, inet_ntoa(ip_addr));
    rv = apr_time_exp_lt(&lt_time, forbidden_time);
    if(rv != APR_SUCCESS){
        return rv;
    }
    
    log_str = apr_pcalloc(dod_preq, max_len);
    apr_snprintf(log_str, max_len, dod_forbidden_log_fmt, ip_str, lt_time.tm_year+1900, lt_time.tm_mon+1, lt_time.tm_mday, lt_time.tm_hour, lt_time.tm_min, lt_time.tm_sec); //ignore error!
    
    slen = strlen(log_str);
    slen_ptr = &slen;

    rv = apr_file_write(dod_forbidden_log_ptr, log_str, slen_ptr);

    if(rv != APR_SUCCESS){
        return rv;
    }
    rv = apr_file_flush(dod_forbidden_log_ptr);
    return rv;
}
